/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package controller;

import digitrencer.CompositeElement;
import digitrencer.Model;
import digitrencer.Wire;
import view.View;
import java.io.File;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.awt.Point;
import java.util.StringTokenizer;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

/**
 * A hálózat beolvasását végzi az XML formátumú fájlból.
 * @author Balázs
 */
public class XMLParser {
    /**
     * A DocumentBuilder objektum legyártásához szükséges.
     */
    private DocumentBuilderFactory dbFactory;
    /**
     * Az XML fájl beolvasását végző objektum.
     */
    private DocumentBuilder parser;
    /**
     * A View referenciája, amelyhez az XMLParser hozzáadja a VisualElement
     * típusú objektumokat.
     */
    private View view;
    /**
     * A naplófájl referenciája, amennyiben az létezik.
     */
    private PrintWriter logfile;
    /**
     * A Controller inputs listájára mutató referencia.
     */
    private ArrayList<Integer> inputs;
    /**
     * A Controller outputs listájára mutató referencia.
     */
    private ArrayList<Integer> outputs;

    /**
     * Konstruktor, inicializálja az XMLParser objektumot.
     * @param view A view objektum rererenciája.
     * @param inputs A Controller inputs listájának referenciája.
     * @param outputs A Controller outputs listájának referenciája.
     * @param logfile A kimeneti fájl.
     * @throws ParserConfigurationException
     */
    public XMLParser(View view, ArrayList<Integer> inputs, ArrayList<Integer> outputs, PrintWriter logfile)
            throws ParserConfigurationException
    {
        this.view = view;
        this.inputs = inputs;
        this.outputs = outputs;
        this.logfile = logfile;
        dbFactory = DocumentBuilderFactory.newInstance();
        dbFactory.setIgnoringElementContentWhitespace(true);
        parser = dbFactory.newDocumentBuilder();
    }

    /**
     * Az XML fájl beolvasását végző függvény. Lényegében ez építi fel a teljes hálózatot.
     * @param file A beolvasni kívánt XML fájl.
     * @return A felépített Model.
     * @throws SAXException Amennyiben az XML feldolgozása során hiba történt, pl hibás szintaxis esetén.
     * @throws IOException Amennyiben nem sikerült a fájl olvasása.
     * @throws NoSuchFieldException Hiányzó mező, attribútum vagy kompozit elem definíció esetén.
     * @throws IllegalArgumentException Nem megfelelő jelgenerátor sorozat vagy pin esetén.
     */
    public Model LoadXML(File file) throws SAXException, IOException,
            NoSuchFieldException, IllegalArgumentException
    {
        // Nem kell konzolról is működnie.
        Document doc = parser.parse(file);
        org.w3c.dom.Element root = doc.getDocumentElement(); // A gyökér elem lekérése
        if (!root.getNodeName().equals("root")) {
            throw new NoSuchFieldException("A gyökér elem neve nem \"root\", a fájl feltehetően nem ehhez a programhoz készült.");
        }
        Model model = new Model(new Wire[0], new Wire[0]);   // A model létrehozása
        buildCompositeElement(model, root, true);
        return model;
    }

    /**
     * Egy kompozit elem (vagy akár maga a Model) felépítését végzi.
     * A kompozit elem konstruktorát a függvényen kívül kell meghívni, ez a függvény csak
     * hozzáadja a megadott kompozit elemhez az építőelemeket.
     * @param ce A felépítendő kompozit elem.
     * @param node Az XML fájl gyökér eleme, vagy a kompozit elem definíciója.
     * @param hasGraphics Igaz, ha az elemhez tartozik grafikus információ.
     * @throws NoSuchFieldException Hiányzó mező, attribútum vagy kompozit elem definíció esetén.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     * @throws IllegalArgumentException Nem megfelelő jelgenerátor sorozat vagy pin esetén.
     */
    private void buildCompositeElement(CompositeElement ce, org.w3c.dom.Element node, boolean hasGraphics)
            throws NoSuchFieldException, NumberFormatException, IllegalArgumentException
    {
        // A wires mező lekérdezése, a vezetékek hozzáadása
        NodeList nl = getChildrenByTagName(node, "wires");
        if (nl == null || nl.getLength() == 0) {
            // Amennyiben nincs wires mező
            String errorString;
            if (node.getNodeName().equals("root")) {
               //errorString = "The root element is missing a \"wires\" field";
               errorString = "A gyökér elemnek nincs \"wires\" mezője.";
            } else {
               //errorString = "Composite element \"" + node.getAttribute("type") + "\"is missing a \"wires\" field";
                errorString = "A(z) \"" + node.getAttribute("type") + "\" típusú kompozit elemnek nincs \"wires\" mezője.";
            }
            throw new NoSuchFieldException(errorString);
        }
        org.w3c.dom.Element field_wires = (org.w3c.dom.Element)nl.item(0);
        int num_wires = 0;
        try {
            num_wires = parseIntWithTrim(field_wires.getFirstChild().getNodeValue());
            for (int i = 0; i < num_wires; i++) ce.addWire();
        } catch (NullPointerException e) {
            //throw new NoSuchFieldException("\"wires\" field is empty.");
            throw new NoSuchFieldException("A \"wires\" mező üres.");
        }

        // A VisualWire-ök beolvasása
        if (hasGraphics) {
            nl = getChildrenByTagName(node, "wire");
            if (nl != null && nl.getLength() == num_wires) {
                for (int i = 0; i < nl.getLength(); i++) {
                    org.w3c.dom.Element field_wire = (org.w3c.dom.Element)nl.item(i);
                    parseVisualWire(field_wire);
                }
            } else {
                throw new NoSuchFieldException("Valamelyik vezetéknek nincsenek definiálva a töréspontjai.");
            }
            
        }

        // Az element-ek beolvasása
        nl = getChildrenByTagName(node, "element");
        if (nl != null) {
            for (int i = 0; i < nl.getLength(); i++) {
                org.w3c.dom.Element field_element = (org.w3c.dom.Element)nl.item(i);
                parseElement(ce, field_element, hasGraphics);
            }
        }

        // A composite_element-ek beolvasása
        nl = getChildrenByTagName(node, "composite_element");
        if (nl != null) {
            for (int i = 0; i < nl.getLength(); i++) {
                org.w3c.dom.Element field_composite = (org.w3c.dom.Element)nl.item(i);
                parseCompositeElement(ce, field_composite, hasGraphics);
            }
        }
    }

    /**
     * Egy (nem kompozit) elem beolvasását végzi, majd beilleszti az őt tartalmazó kompozit elembe.
     * @param parent A kompozit elem, amelybe beillesztjük az új alkatrészt.
     * @param node Az új alkatrész leírása az XML fájlban.
     * @param hasGraphics Igaz, ha az elemhez tartozik grafikus információ.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     * @throws NoSuchFieldException Üres mező esetén.
     * @throws IllegalArgumentException Nem megfelelő jelgenerátor sorozat vagy pin esetén.
     */
    private void parseElement(CompositeElement parent, org.w3c.dom.Element node, boolean hasGraphics)
            throws NumberFormatException, NoSuchFieldException, IllegalArgumentException
    {
        String type = node.getAttribute("type");

        // A kapcsolódó vezetékek beolvasása
        int[] elementInputs, elementOutputs;
        elementInputs = createArrayFromChildNodes(node, "in");
        elementOutputs = createArrayFromChildNodes(node, "out");
        int ID = parent.addElement(type, elementInputs, elementOutputs);

        // Inputok és outputok kiíratása, regisztrálása
        if (type.equals("switch") || type.equals("generator")) {
            logMessage("I/O\t:\tInput#"+ID+"("+type+")");
            inputs.add(ID);
        }
        if (type.equals("led") || type.equals("oscilloscope")) {
            logMessage("I/O\t:\tOutput#"+ID+"("+type+")");
            outputs.add(ID);
        }

        // Szekvencia beállítása jelgenerátor esetén
        // Ellenőrizni: még véletlenül se forduljon elő, hogy egy CompositeElement-ben (helytelenül)
        // deklarált Input vagy Output a listába kerüljön
        String seq = getTextValue(node, "sequence");
        if (seq != null) {
            if (type.equals("generator")) {
                Model model = (Model)parent;
                model.setSequence(ID, seq);     // ismét egy veszélyes rész, ellenőrizni kell
            }
        }

        // Pin beállítása Tag esetén
        String str_pin = getTextValue(node, "pin");
        if (str_pin != null) {
            int pin = parseIntWithTrim(str_pin);
            if (type.equals("tag")) {
                parent.setPin(ID, pin);
            }
        }

        if (hasGraphics) {
            parseVisualElement(node, ID);
        }
    }

    /**
     * Egy kompozit elem beolvasását végzi a fájlból. Megkeresi a kompozit elem definícióját,
     * felépíti azt, majd behelyezi az őt tartalmaző kompozit elembe.
     * @param parent Az új kompozit elemet tartalmazó kompozit elem.
     * @param node Az új kompozit elem leírása az XML fájlban.
     * @param hasGraphics Igaz, ha az elemhez tartozik grafikus információ.
     * @throws NoSuchFieldException Hiányzó mező, attribútum vagy kompozit elem definíció esetén.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     * @throws IllegalArgumentException Nem megfelelő jelgenerátor sorozat vagy pin esetén.
     */
    private void parseCompositeElement(CompositeElement parent, org.w3c.dom.Element node, boolean hasGraphics)
            throws NoSuchFieldException, NumberFormatException, IllegalArgumentException
    {
        String type = node.getAttribute("type");
        if (type == null || type.equals("")) {  // Ha nincs definiálva a típusa
            //throw new NoSuchFieldException("Composite element is missing a \"type\" attribute");
            throw new NoSuchFieldException("A kompozit elemnek nincs \"type\" attribútuma.");
        }

        // Definíció megkeresése
        org.w3c.dom.Element definition = findDefinition(node, type);
        if (definition == null) {
            //throw new NoSuchFieldException("Composite element \""+type+"\" has no definiton");
            throw new NoSuchFieldException("A \""+type+"\" típusú kompozit elemnek nincs definíciója.");
        }
        CompositeElement child = new CompositeElement(new Wire[0], new Wire[0]);
        buildCompositeElement(child, definition, false);

        // A kapcsolódó vezetékek beolvasása
        int[][] elementInputs, elementOutputs;
        elementInputs = createDoubleArrayFromChildNodes(node, "in", "pin");
        elementOutputs = createDoubleArrayFromChildNodes(node, "out", "pin");
        int ID = parent.addComopsiteElement(child, elementInputs, elementOutputs);

        if (hasGraphics) {
            parseVisualElement(node, ID);
        }
    }

    /**
     * Egy vezeték grafikus adatainak beolvasását végzi a fájlból. A fájlban
     * definiálhatunk egyágó vagy többágú vezetéket.
     * @param node A vezeték leírása az XML fájlban.
     * @throws NumberFormatException Hibás számformátum esetén.
     * @throws NoSuchFieldException Hiányzó vagy üres mező esetén.
     * @throws IllegalArgumentException Nem megfelelő ág-azonosító esetén.
     */
    private void parseVisualWire(org.w3c.dom.Element node)
            throws NumberFormatException, NoSuchFieldException, IllegalArgumentException
    {
        // ID beolvasása
        int ID = parseIntWithTrim(getTextValue(node, "id"));
        Point[][] breakpoints;

        // Ágak feldolgozása
        NodeList branchlist = getChildrenByTagName(node, "branch");
        if (branchlist != null && branchlist.getLength() > 0) {
            // Amennyiben vannak ágak definiálva
            breakpoints = new Point[branchlist.getLength()][];
            for (int i = 0; i < branchlist.getLength(); i++) {
                org.w3c.dom.Element field_branch = (org.w3c.dom.Element)branchlist.item(i);
                int branch_num; // Az ág azonosítója
                try {
                    branch_num = parseIntWithTrim(field_branch.getFirstChild().getNodeValue());
                } catch (NullPointerException e) {
                    throw new NoSuchFieldException("Az ághoz nincs megadva azonosító.");
                }
                if (branch_num < 0 || branch_num >= branchlist.getLength())
                    throw new IllegalArgumentException("Az ág azonosítója a csak 0 és " + branchlist.getLength() + " között lehet.");
                breakpoints[branch_num] = parseBranch(field_branch);
            }
        } else {
            // Ha nincsenek megadva branch-ek
            breakpoints = new Point[1][];
            breakpoints[0] = parseBranch(node);
        }

        // Ellenőrizzük, hogy mindegyik ágat definiáltuk-e
        for(int i = 0; i < breakpoints.length; i++) {
            if(breakpoints[i] == null) {
                throw new NoSuchFieldException("A(z) " + i + ". ágnak nincsenek megadva töréspontok.");
            }
        }

        // Hozzáadjuk a view-hoz
        view.addVisualWire(ID, breakpoints);
    }

    /**
     * Egy elem beolvasását végzi az XML fájlból. Egyszerű és kompozit elemekre
     * is ezt hívjuk meg.
     * @param node Az alkatrész leírása az XML fájlban.
     * @param ID Az alkatrész ID-ja a modellben.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     * @throws NoSuchFieldException Hiányzó vagy üres mező esetén.
     * @throws IllegalArgumentException A view dobja nem megfelelő argumentum esetén.
     */
    private void parseVisualElement(org.w3c.dom.Element node, int ID)
            throws NumberFormatException, NoSuchFieldException, IllegalArgumentException
    {
        String type;
        if (node.getNodeName().equals("composite_element")) {
            // Ha kompozit elemről van szó, hozzáfűzzük a típusát a type mezőhöz
            type = "composite\n" + node.getAttribute("type");
        } else {
            // Ha nem kompozit elem, akkor a type mező a típus
            type = node.getAttribute("type");
        }
        // Beolvassuk a pozícióját
        Point pos = parsePoint(getTextValue(node, "pos"));

        // Beolvassuk a ki- és bemeneteit
        NodeList inputList = getChildrenByTagName(node, "in");
        NodeList outputList = getChildrenByTagName(node, "out");
        int numInputs, numOutputs;
        try {
            // Meghatározzuk a ki- és bemenetek számát
            numInputs = inputList.getLength();
            numOutputs = outputList.getLength();
        } catch (NullPointerException e) {  // A biztonság kedvéért
            numInputs = 0;
            numOutputs = 0;
        }

        // Hozzáadjuk a view-hoz
        view.addVisualElement(ID, pos, type, numInputs, numOutputs);

        // Beállítjuk a bemeneti vezetékek végpontjait
        if (inputList != null) {
            for (int i = 0; i < inputList.getLength(); i++) {
                org.w3c.dom.Element field_in = (org.w3c.dom.Element)inputList.item(i);
                int wire = parseIntWithTrim(field_in.getFirstChild().getNodeValue());
                int branch = 0;
                if (field_in.hasAttribute("branch")) {
                    // Amennyiben meg van adva az ág
                    branch = parseIntWithTrim(field_in.getAttribute("branch"));
                }
                view.setEndPoint(wire, view.getInputPos(ID, i), branch);
            }
        }

        // Beállítjuk a kimeneti vezetékek kezdőpontjait
        if (outputList != null) {
            for (int i = 0; i < outputList.getLength(); i++) {
                org.w3c.dom.Element field_in = (org.w3c.dom.Element)outputList.item(i);
                int wire = parseIntWithTrim(field_in.getFirstChild().getNodeValue());
                view.setStartingPoint(wire, view.getOutputPos(ID, i));
            }
        }

        /* Megjegyzés: itt nem ellenőrizzük, hogy az elemek megfelelő módon
           csatlakoznak-e az egyes ágakhoz, tehát hogy minden ágat felhasználunk-e,
           és hogy nem csatlakozik-e több elem ugyanarra az ágra. */
    }

    /**
     * Egy vezeték valamely ágának töréspontjait olvassa be az XML fájlból.
     * @param node Az ág töréspontjait tartalmazó XML elem.
     * @return A töréspontok tömbje az adott ágban, üresen hagyva a helyet a
     * kezdő- és végpontoknak.
     * @throws NoSuchFieldException Hiányzó vagy üres mező esetén.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     */
    private Point[] parseBranch(org.w3c.dom.Element node)
            throws NoSuchFieldException, NumberFormatException
    {
        Point[] breakpoints;
        NodeList bplist = getChildrenByTagName(node, "bp");
        if (bplist != null) {
            // Kettővel hosszabb tömböt hozunk létre, hogy elférjen a kezdő- és végpont
            breakpoints = new Point[bplist.getLength() + 2];
            for (int j = 0; j < bplist.getLength(); j++) {
                org.w3c.dom.Element field_bp = (org.w3c.dom.Element)bplist.item(j);
                Point bp;
                try {
                    // Beolvassuk a töréspont pozícióját
                    bp = parsePoint(field_bp.getFirstChild().getNodeValue());
                } catch (NullPointerException e) {
                    throw new NoSuchFieldException("Üres töréspont!");
                }
                breakpoints[j+1] = bp;
            }
        } else breakpoints = new Point[2]; // A biztonság kedvéért
        return breakpoints;
    }

    /**
     * Megkeresi egy kompozit elem definícióját.
     * @param composite_node A kompozit elem példányosítását tartalmazó XML elem.
     * @param type A kompozit elem típusa.
     * @return A kompozit elem definícióját tartalmazó XML elem. Ha nincs ilyen, akkor null.
     */
    private org.w3c.dom.Element findDefinition(org.w3c.dom.Element composite_node, String type)
    {
        org.w3c.dom.Element parent = (org.w3c.dom.Element)composite_node.getParentNode();
        if (parent == null) return null;
        NodeList nl = getChildrenByTagName(parent, "define");
        if (nl != null) {
            for (int i = 0; i < nl.getLength(); i++) {
                org.w3c.dom.Element node = (org.w3c.dom.Element)nl.item(i);
                String define_type = node.getAttribute("type");
                if (type.equals(define_type)) return node;
            }
        }
        return null;
    }

    /**
     * Egy egész számokat tartalmazó tömböt készít egy XML elem adott típusú mezőiből.
     * Ez az alkatrészek be- és kimeneteinek feldolgozását könnyíti meg.
     * @param node Az XML elem, amelyet feldolgozunk.
     * @param child_type Megadja, hogy az XML elem milyen típusú mezőiből építsük fel a listát.
     *      Ez jellemzően "in" vagy "out".
     * @return A felépített tömb.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     * @throws NoSuchFieldException Üres mező esetén.
     */
    private int[] createArrayFromChildNodes(org.w3c.dom.Element node, String child_type)
            throws NumberFormatException, NoSuchFieldException
    {
        int[] array;
        NodeList nl = getChildrenByTagName(node, child_type);
        if (nl != null) {
            array = new int[nl.getLength()];
            for (int i = 0; i < nl.getLength(); i++) {
                org.w3c.dom.Element field = (org.w3c.dom.Element)nl.item(i);
                try {
                    int ID = parseIntWithTrim(field.getFirstChild().getNodeValue());
                    array[i] = ID;
                } catch (NullPointerException e) {
                    //throw new NoSuchFieldException("\"" + child_type + "\" is empty.");
                    throw new NoSuchFieldException("A(z) \"" + child_type + "\" mező üres.");
                }
            }
        } else {
            array = new int[0];  // A biztonság kedvéért
        }
        return array;
    }

    /**
     * Egy egész számokat tartalmazó kétdimenziós tömböt készit egy XML elem adott típusú mezőiből..
     * A kompozit elem be- és kimeneteinek feldolgozásánál használjuk, ahol a vezetékek mellett attribútumban
     * meg van adva, hogy mely vezeték meylik lábra csatlakozik. Az elkészült kétdimenziós tömb
     * érték-attribútum párokat tartalmaz.
     * @param node Az XML elem, amelyet feldolgozunk.
     * @param child_type Megadja, hogy az XML elem milyen típusú mezőiből építsük fel a listát.
     *      Ez jellemzően "in" vagy "out".
     * @param attribute Megadja a feldolgozni kívánt attribútum nevét.
     *      Ez jellemzően "pin".
     * @return A felépített kétdimenziós tömb.
     * @throws NoSuchFieldException Hiányzó attribútum esetén.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     */
    private int[][] createDoubleArrayFromChildNodes(org.w3c.dom.Element node, String child_type, String attribute)
            throws NoSuchFieldException, NumberFormatException
    {
        int[][] array;
        NodeList nl = getChildrenByTagName(node, child_type);
        if (nl != null) {
            array = new int[nl.getLength()][2];
            for (int i = 0; i < nl.getLength(); i++) {
                org.w3c.dom.Element field = (org.w3c.dom.Element)nl.item(i);
                try {
                    int ID = parseIntWithTrim(field.getFirstChild().getNodeValue());
                    array[i][1] = ID;
                } catch (NullPointerException e) {
                    //throw new NoSuchFieldException("\"" + child_type + "\" is empty.");
                    throw new NoSuchFieldException("A(z) \"" + child_type + "\" mező üres.");
                }
                String str_att = field.getAttribute(attribute);
                if (str_att.equals(""))     // Ha nincs megadva pin attribútum
                    //throw new NoSuchFieldException("Pin undefined for composite element of type \"" + node.getAttribute("type") + "\"");
                    throw new NoSuchFieldException("Hiányzó láb a(z) \"" + node.getAttribute("type") + "\" típusú kompozit elemnél.");
                int att = parseIntWithTrim(str_att);
                array[i][0] = att;
            }
        } else {
            array = new int[0][2];  // A biztonság kedvéért
        }
        return array;
    }

    /**
     * Egy XML elem adott típusú mezőjének értékét adja vissza.
     * @param node Az XML elem.
     * @param tagName Az XML elem mezőjének neve.
     * @return A kiolvasott érték. Amennyiben a megadott mező nem létezik, a visszatérési érték null.
     * @throws NoSuchFieldException Üres mező esetén.
     */
    private String getTextValue(org.w3c.dom.Element node, String tagName)
            throws NoSuchFieldException
    {
		String textVal = null;
		NodeList nl = getChildrenByTagName(node, tagName);
		if(nl != null && nl.getLength() > 0) {
			org.w3c.dom.Element el = (org.w3c.dom.Element)nl.item(0);
            try {
                textVal = el.getFirstChild().getNodeValue();
            } catch (NullPointerException e) {
                //throw new NoSuchFieldException("\"" + tagName + "\" is empty.");
                throw new NoSuchFieldException("A(z) \"" + tagName + "\" mező üres.");
            }
		}
		return textVal;
	}

    /**
     * Egy XML elem adott típusú gyerekeit adja vissza. Ez helyettesíti a getElementsByTagName
     * függvényt, mivel az nem csak a közvetlen gyerekeit adja vissza az XML elemnek.
     * @param node Az XML elem, amelynek a gyerekeit vizgáljuk.
     * @param tagName A gyerekek típusa.
     * @return A megfelelő típusú gyerekeket tartalmazó NodeList.
     */
    private NodeList getChildrenByTagName(org.w3c.dom.Element node, String tagName)
    {
        NodeList nl = node.getElementsByTagName(tagName);   // Az összes adott típusú elem lekérdezése
        if (nl == null) return nl;
        NodeArrayList children = new NodeArrayList();
        for (int i = 0; i < nl.getLength(); i++) {
            org.w3c.dom.Element child = (org.w3c.dom.Element)nl.item(i);
            if (child.getParentNode().equals(node)) {   // Szűrés, akkor marad a listában, ha a szülője node
                children.add(child);
            }
        }
        return children;

    }

    /**
     * Kiírja a megadott üzenetet a standard kimenetre és a logfile-ba.
     * @param msg A kiírandó üzenet.
     */
    private void logMessage(String msg)
    {
//        System.out.println(msg);
        if (logfile != null) {
            logfile.println(msg);
        }
    }

    /**
     * Egy "x, y" formátumú sztringet Point típussá konvertál.
     * @param str A koordinátákat tartalmazó sztring.
     * @return A pozíció Point típusban megadva.
     * @throws NumberFormatException Nem megfelelő számformátum, vagy nem 2
     * koordináta esetén.
     */
    private static Point parsePoint(String str) throws NumberFormatException
    {
        StringTokenizer tokenizer = new StringTokenizer(str, ",");
        if (tokenizer.countTokens() != 2)
            throw new NumberFormatException("A pozíciót két koordinátával kell megadni!");
        int x = parseIntWithTrim(tokenizer.nextToken());
        int y = parseIntWithTrim(tokenizer.nextToken());
        return new Point(x, y);
    }

    /**
     * Egy sztringet számmá alakít úgy, hogy előtte levágja a sztring elején és
     * végén lévő whitespace-eket.
     * @param str A számmá alakítndó szting.
     * @return A sztringből kinyert szám.
     * @throws NumberFormatException Nem megfelelő számformátum esetén.
     */
    private static int parseIntWithTrim(String str) throws NumberFormatException
    {
        str = str.trim();
        return Integer.parseInt(str);
    }

    /**
     * Egy NodeList interfészt implementáló segédosztály.
     */
    private class NodeArrayList extends ArrayList<Node> implements NodeList
    {
        @Override
        public Node item(int index) {
            return get(index);
        }

        @Override
        public int getLength() {
            return size();
        }
    }
}
